#include <linux/mm.h>
#include <linux/ioremap.h>
#include <linux/vmalloc.h>

#include "inc/io.h"

#ifndef IOREMAP_START
#define IOREMAP_START VMALLOC_START
#define IOREMAP_END VMALLOC_END
#endif

bool is_ioremap_addr(const void *x)
{
    unsigned long addr = (unsigned long)(x);

    return addr >= IOREMAP_START && addr < IOREMAP_END;
}

void *generic_ioremap_prot(phys_addr_t phys_addr, size_t size, pgprot_t prot)
{
    unsigned long offset, vaddr;
    phys_addr_t last_addr;
    struct vm_struct *area;

    /* Disallow wrap-around or zero size */
    last_addr = phys_addr + size - 1;
    if (!size || last_addr < phys_addr)
        return NULL;

    /* Page-align mappings */
    offset = phys_addr & (~PAGE_MASK);
    phys_addr -= offset;
    size = PAGE_ALIGN(size + offset);

    area = __get_vm_area_caller(size, VM_IOREMAP, IOREMAP_START,
                                IOREMAP_END, __builtin_return_address(0));
    if (!area)
        return NULL;
    vaddr = (unsigned long)area->addr;
    area->phys_addr = phys_addr;

    if (ioremap_page_range(vaddr, vaddr + size, phys_addr, prot))
    {
        free_vm_area(area);
        return NULL;
    }

    return (void *)(vaddr + offset);
}

void generic_iounmap(volatile void *addr)
{
    void *vaddr = (void *)((unsigned long)addr & PAGE_MASK);

    if (is_ioremap_addr(vaddr))
        vunmap(vaddr);
}
